Module SYSTEM contains certain procedures that are necessary to implement low-level operations. It is strongly recommended to restrict its use to specific low-level modules, as such modules are inherently non-portable. SYSTEM is not considered as part of Oberon/L proper.
The procedures contained in module SYSTEM are listed in the following table. v stands for a variable. x, y, and n stands for expressions. T stands for a type. P stands for a procedure. M[a] stands for memory value at address a.
Function procedures
Name Argument types Result type Description
ADR(v) any LONGINT address of variable v
ADR(P) P:PROCEDURE LONGINT address of Procedure P
ADR(T) T: a record type LONGINT address of Descriptor of T
LSH(x, n) x, n: integer type type of x logical shift (n > 0: left, n < 0: right)
ROT(x, n) x, n: integer type type of x rotation (n > 0: left, n < 0: right)
VAL(T, x) T, x: any type T x interpreted as of type T
Proper procedures
Name Argument types Description
GET(a, v) a: LONGINT; v: any basic type, v := M[a]
pointer type, procedure type
PUT(a, x) a: LONGINT; x: any basic type, M[a] := x
pointer type, procedure type
GETREG(n, v) n: integer constant, v: any basic type, v := Register n
pointer type, procedure type
PUTREG(n, x) n: integer constant, x: any basic type, Register n := x
PUT, PUTREG, and MOVE may crash Oberon/F and/or Windows when not used properly.
Never use VAL to assign a value to an Oberon/F pointer. Doing this would corrupt the garbage collector, with fatal consequences.
Compiler Issues
Typesize, Alignment and Calling Conventions
The size of Oberon basetypes is listed below. Composite types (records, arrays) are constructed out of these basetypes. Record fields and array elements whose sizes are larger than one byte are aligned to the next word (two bytes) boundary.
Type Size
SHORTINT 1 byte
INTEGER 2 bytes
LONGINT 4 bytes
REAL 4 bytes
LONGREAL 8 bytes
CHAR 1 byte
BOOLEAN 1 byte
SET 4 bytes
POINTER 4 bytes
All procedure calls in Oberon/F are stack-based. The parameters are pushed according to the definition in Inside Macintosh (I-90). The space for a function result is allocated on the stack by the compiler before pushing parameters and calling the function. Used registers are saved by the caller and restored after returning from the called procedure. To prevent the stack from growing into the heap the stackpointer is checked upon every entry into a procedure, a trap is generated if the test fails.
Runtime Type System
All dynamically allocated records contain a hidden field, which is called the type tag. The type tag points to the type descriptor that contains all type information needed at runtime. Calls of type-bound procedures, type tests and the garbage collector all use the information which is stored in the type descriptor. The type descriptor is allocated only once for every record type in the system.
Dynamically allocated arrays use a size descriptor to check the bounds of indexes. The size descriptor also contains the type tag of the elementtype. For every single dynamic array a size descriptor is needed.
System Flags
The import of module SYSTEM allows to override some default behavior of the compiler by the usage of system flags. System flags are used to configure type- and procedure declarations. The extended syntax is given below.
Type = Qualident
| ARRAY ["[" SysFlag "]"] [ConstExpr {"," ConstExpr}] OF Type
| RECORD ["[" SysFlag "]"] ["("Qualident")"] FieldList {";" FieldList} END
UNTAGGED 1 No type tag and no type descriptor is allocated. NEW is not allowed. No type-bound procedures are allowed. Pointers to this record type and extensions of this record type inherit the attribute of being untagged.
System flags for Array types:
Flag Value Description
UNTAGGED 1 No typetag and no type descriptor is allocated. NEW is not allowed. Pointers to this array type inherit the attribute of being untagged. Only one-dimensional untagged open arrays are allowed. Index bounds are not checked.
System flags for Pointer types:
Flag Value Description
UNTAGGED 1 Not traced by the garbage collector. No type-bound procedures are allowed. Must point to an untagged record.
HANDLE 2 Eliminates the need to define an intermediate pointer type. Automatic double dereference when accessing fields of the associated record or array. Handles are always untagged.
If the value of the intermediate pointer is required, use the function procedure SYSTEM.ADR with the first record field as parameter,
e.g. ptr := SYSTEM.ADR(myPicHandle.picSize).
System flags for VAR parameters:
Flag Value Description
NILCOMP 1 NIL is accepted as formal parameter. Used in interfaces to C functions with pointer type parameters.
System flags for Procedures:
Flag Value Description
CODE 1 Definition of a Code procedure (see below).
CALLBACK 2 The registers (D0-D7, A0-A4) are saved and restored. Use this sysflag for procedures installed as callback functions in the Macintosh Toolbox,
e.g. procedures that are assigned to the parameter actionProc in MacControlMgr.TrackControl.
NOSTKCHK 4 Disables the stack overflow check upon procedure entry. Necessary for procedures called at interrupt time, e.g. handler in VBLTask (MacVBLMgr.VBLTask.vblAddr).
INTERFACE -20 Definition of a Interface procedure (see below "Using Code Fragments in Oberon/F Modules").
Code Procedures
Code procedures make it possible to use special code sequences not generated by the compiler. They are declared using the following special syntax:
The list of constants declared with the procedure is interpreted as a byte string and directly inserted in the code whenever the procedure is called. If a parameter list is supplied, the actual parameters are pushed on the stack from left to right. Parameters on the stack must be removed by the procedure.
PROCEDURE NewPtr* (byteCount: Size): Ptr; (* Routine with glue *)
VAR p: Ptr;
BEGIN
SYSTEM.PUTREG(D0, byteCount);
newPtr;
SYSTEM.GETREG(D0, err);
SYSTEM.GETREG(A0, p);
RETURN p
END NewPtr;
Interfacing the Macintosh Toolbox
Interface Modules
The Macintosh Toolbox is accessed through a set of interface modules, together they form the subsystem Mac. Interface modules are common Oberon modules, but importing a interface module makes your code Mac OS dependent. Every interface module provides access to one Macintosh Toolbox Manager. All calls to routines in the interface modules are stack-based, the interface modules provide the glue code wherever it is necessary.
Type extension is used for defining hierarchical types. This makes access to record fields more natural, first because there is no need for a distinction between Ptr and Peek. Peeks are not defined in the interface modules as they would never be used. Second you can access the fields of basetypes directly without having to specify the record first.
An example:
DialogPtr* = POINTER TO DialogRecord;
DialogRecord* = RECORD (MacWindowMgr.WindowRecord)
items*: MacTypes.Handle;
textH*: MacTextEdit.TEHandle;
editField*: INTEGER;
editOpen: INTEGER; (* used internally, therefore not exported *)
aDefItem*: INTEGER
END;
VAR dlg: DialogPtr;
rect := dlg.portRect; (* portRect is a field of GrafPort *)
items := dlg.items; (* in Pascal this would be: DialogPeek(dlg).items *)
MacWindowMgr.HideWindow(dlg); (* dlg is an extension of WindowPtr *)
Common types such as Str255 and Handle are defined in the module MacTypes. The interface module MacTypes also exports procedures to convert zero-terminated strings (Oberon, C) into pascal-type strings with a leading length byte.
We refer to the examples
ObxMacHello
ObxMacPICTView
and
ObxMacEditor
on how to create and link pure Macintosh applications.
Using Shared Libraries in Oberon/F Modules
Oberon/F supports the new Apple standard for shared liraries, the Code Fragment Manager (CFM). Therefore the Code Fragment Manager must be installed on your Macintosh if you want to use shared libraries in Oberon/F. Shared libraries that reside in code fragments can be imported in Oberon like normal Oberon modules. This holds for Macintosh system libraries (InterfaceLib, MathLib, ...) as well as for any custom shared library written in any programming language. Be aware that the safety qualities of Oberon (no dangling pointers, strict type-checking, etc) are partially lost if you interface to a shared library written in another programming language. In addition, some rules must be followed when working with shared libraries:
Symbol Files
As for any imported module, the compiler needs a symbol for each shared library. You can make symbol files for shared libraries by compiling an interface definition module like the example at the end of this text. The module name must match the library name exactly. The code file generated by the compilation of such a module is useless and must be discarded. If it is not deleted the loader will load and link the empy module instead of the shared library.
Procedure names in the interface module must match the corresponding names in the shared library exactly. Types and constants may have arbitrary names because they are not imported at run-time.
Always use untagged records as replacements for external structures to avoid the allocation of a type tag. The system flag [1] marks a type as untagged (no type information is available at run-time).
Procedures
For parameters of type POINTER TO T it is often better to use a variable parameter of type T rather than to declare a corresponding pointer type. Declare the VAR parameter with system flag [1] if NIL must be accepted as legal actual parameter.
The Oberon/F Linker
Stand-alone applications are linked using DevLinker. DevLinker copies the relevant information out of the code files into resources of type "oOBF". Additional boot information is stored in resources of type "bOBF". A "CODE"-resource containing the boot loader is also generated. Resources with other types are left untouched, you don't need to specify a seperate resource file from which to copy all resources.
application: Filename of the application. A new file is generated if no file with the specified name exists otherwise the old file is reused. Existing resources of the types "CODE", "oOBF" and "bOBF" are removed before linking takes place.
module0..i: Modules linked into the application. Imported modules are linked automatically, you need to list only the top modules. For instance, all necessary interface modules are linked.
mainmodule: At startup the bodies of the main module and all underlying modules are executed. Usually the toplevel module is specified, which should also appear as the last in the list of linked modules. Modules on the same level are initialized in the order in which they appear in the link list or import list of the importing module. The mainmodule should always be the last in the modulelist with one exception: the Kernel is always first in the module list (see "Using NEW and Garbage Collection in Your Applications").
Linking Oberon/F applications
If you want to distribute an application you have written in Oberon/F, you may want to link all modules into a single file for distribution. In this case you need to link the framework to your application. To illustrate the necessary actions we will give an example. First duplicate the OberonF application in the Finder and rename it accordingly, in our case "Patterns". Adapt the module Config to your needs, e.g. delete the call which automatically opens the Log text. Then link the framework with your modules into the new application file. You may need to distribute some additional files with your application such as forms and the Menu text.
Using NEW and Garbage Collection in Your Applications
If you are calling NEW in your application and thereby implicitly use the garbage collector, you must link the Kernel into the application. The NEW-procedure is implemented in the kernel, the compiler just generates the code to call this procedure. So every module using NEW has a hidden import of the kernel. The kernel must be the first module in the list of linked modules and should be specified as the main module. During initialization, the kernel seizes control from the boot loader and traverses the list of loaded modules which has been generated by the boot loader and calls subsequently all module bodies.
Don't call MacOSUtils.ExitToShell directly when "importing" the kernel, call Kernel.Quit with parameter zero instead to assure that occupied system resources get properly released before the application is terminated.
Programs don't need to call the garbage collector explicitly. If the NEW-procedure cannot satisfy a request for heap space it calls the garbage collector internally before allocating a new heap block from the Macintosh Memory Manager. The garbage collector marks pointers in stackframes and is able to run anytime except at interrupt-time. As a consequence calling NEW is not allowed during interrupt handling.